Skip to content

refactor: unified polymorphic domain + registry#1983

Merged
shrugs merged 42 commits intomainfrom
refactor/ensv1-domain-model
Apr 28, 2026
Merged

refactor: unified polymorphic domain + registry#1983
shrugs merged 42 commits intomainfrom
refactor/ensv1-domain-model

Conversation

@shrugs
Copy link
Copy Markdown
Member

@shrugs shrugs commented Apr 22, 2026

closes #205
closes #1511
closes #1877

shrugs and others added 6 commits April 21, 2026 17:10
Split `RegistryId` into a union of `ENSv1RegistryId`, `ENSv1VirtualRegistryId`,
and `ENSv2RegistryId`. Add corresponding `makeENSv1RegistryId`,
`makeENSv2RegistryId`, and `makeENSv1VirtualRegistryId` constructors, and keep
`makeRegistryId` as a union-returning helper for callsites that genuinely can't
narrow (e.g. client-side cache key reconstruction).

Reshape `ENSv1DomainId` from `Node` to `${ENSv1RegistryId}/${node}` so ENSv1
domains are addressable in the same namegraph model as ENSv1VirtualRegistry.
`makeENSv1DomainId` now takes `(AccountId, Node)` — breaking change for all
callers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the split `v1_domains` + `v2_domains` tables with a single polymorphic
`domains` table keyed by `DomainId` and discriminated by `domainType` enum
(`"ENSv1Domain"` | `"ENSv2Domain"`). Drop `domain.parentId`; ENSv1 parent
traversal now flows through `registryCanonicalDomain` uniformly with ENSv2.
`tokenId` becomes nullable (non-null iff ENSv2).

Make `registries` polymorphic: add `registryType` enum
(`"ENSv1Registry"` | `"ENSv1VirtualRegistry"` | `"ENSv2Registry"`), add nullable
`node` column (non-null iff virtual), replace the unique `(chainId, address)`
constraint with a plain index so virtual Registries keyed by node can share
(chainId, address) with their concrete parent.

Widen `registryCanonicalDomain.domainId` from `ENSv2DomainId` to the unified
`DomainId`.

Add `getENSv1RootRegistryId` / `maybeGetENSv1RootRegistryId` / `maybeGetENSv1Registry`
helpers mirroring the v2 equivalents; narrow v2 helpers to use `makeENSv2RegistryId`.

Update the ensdb-sdk drizzle test to reference the unified `domain` export.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extend `managed-names.ts`: `CONTRACTS_BY_MANAGED_NAME` now maps `Name` to
`{ registry, contracts }`, `getManagedName(contract)` returns
`{ name, node, registry }` so any Registrar / Controller / NameWrapper handler
can resolve the concrete ENSv1 Registry that governs its namegraph. Add the
ENS Root (`""`) Managed Name group covering the mainnet ENSv1Registry and
ENSv1RegistryOld; include each shadow Registry (Basenames, Lineanames) in its
respective Managed Name group. Groups for namespaces that don't ship a given
shadow Registry are omitted entirely.

ENSv1 `handleNewOwner`: upsert the concrete ENSv1 Registry row, pick
`parentRegistryId` as the concrete Registry when `parentNode` is the Managed
Name and as an `ENSv1VirtualRegistry` keyed by `parentNode` otherwise. When
the parent is virtual, also upsert the virtual Registry row and the
`registryCanonicalDomain` self-link so reverse traversal works uniformly with
ENSv2. Combine domain upsert with `rootRegistryOwner` update into one query
via `onConflictDoUpdate`. Canonicalize ENSv1Registry / ENSv1RegistryOld events
through `getManagedName(...).registry` — ENSRegistryWithFallback proxies
reads, so both contracts face the same logical namegraph and should write into
the same Registry ID.

All remaining v1 handlers (Transfer / NewTTL / NewResolver, BaseRegistrar,
NameWrapper, RegistrarController, protocol-acceleration ENSv1Registry /
ThreeDNSToken) update to the two-arg `makeENSv1DomainId(registry, node)`.

ENSv2 `handleRegistrationOrReservation`: switch `makeRegistryId` to
`makeENSv2RegistryId`, add `type: "ENSv2Registry"` to the Registry insert and
`type: "ENSv2Domain"` to the Domain insert.

Update `domain-db-helpers.materializeENSv1DomainEffectiveOwner` to write
through the unified `domain` table. Update the managed-names test to assert
the new `registry` return field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Context & loaders
- Drop `v1CanonicalPath` + `v2CanonicalPath` loaders in favour of a single
  `canonicalPath` loader backed by `getCanonicalPath(domainId)`.

Canonical path
- Replace `getV1CanonicalPath` + `getV2CanonicalPath` with a single recursive
  CTE over `domain` + `registryCanonicalDomain`. Recursion terminates naturally:
  roots have no `registryCanonicalDomain` entry, so the JOIN fails when we
  reach one. Canonicality is decided by the final `tld.registry_id === root`
  check. MAX_DEPTH guards against corrupted state.

Interpreted-name lookup (`get-domain-by-interpreted-name.ts`)
- Collapse the ENSv1 / ENSv2 branches into one `traverseFromRoot(root, name)`
  helper. Both lineages hop via `domain.subregistryId` (ENSv1 Domains now set
  this to their managed VirtualRegistry, symmetric with ENSv2 domains' declared
  subregistries). The starting root picks v1 vs v2 lineage; v1 and v2 registry
  IDs are disjoint, so no cross-contamination.

Find-domains layers
- `base-domain-set.ts`: single select over `domain`; `parentId` derived via
  `registryCanonicalDomain` uniformly for v1 and v2.
- `filter-by-registry.ts`: simplify comment (no v1/v2 distinction).
- `filter-by-canonical.ts`: all domains have a `registryId` now; canonicality
  reduces to `INNER JOIN` against the canonical-registries CTE.
- `filter-by-name.ts`: collapse `v1DomainsByLabelHashPath` +
  `v2DomainsByLabelHashPath` into one CTE over `registryCanonicalDomain`.
- `canonical-registries-cte.ts`: union v1 + v2 roots as base cases; recursive
  step uses `d.subregistry_id` uniformly.

Schemas
- `schema/domain.ts`: `DomainInterfaceRef` becomes a loadable interface with a
  single `ensDb.query.domain.findMany` loader. `DomainInterface = Omit<Domain,
  "tokenId" | "node" | "rootRegistryOwnerId">`. Variant types tightened via
  `RequiredAndNotNull` / `RequiredAndNull` to encode invariants
  (`ENSv1Domain.{node: Node, tokenId: null}`;
  `ENSv2Domain.{tokenId: bigint, node: null, rootRegistryOwnerId: null}`).
  `parent` moves onto the interface via `ctx.loaders.canonicalPath`; expose
  `ENSv1Domain.node` as a first-class GraphQL field.
- `schema/registry.ts`: new `RegistryInterfaceRef` with `ENSv1Registry`,
  `ENSv1VirtualRegistry`, `ENSv2Registry` implementations; shared fields
  (`id`, `type`, `contract`, `parents`, `domains`, `permissions`). `parents`
  uses `eq(domain.subregistryId, parent.id)` for virtual v1 and v2 (both set
  `subregistryId`), and `eq(domain.registryId, parent.id)` for concrete v1.
  `ENSv1VirtualRegistryRef` exposes `node: Node`.
- `schema/query.ts`: `registry(by: {contract})` does a DB lookup filtered by
  `type IN (ENSv1Registry, ENSv2Registry)` — virtual Registries share
  `(chainId, address)` with their concrete parent and aren't addressable via
  contract alone. Dev-only `v1Domains` / `v2Domains` filter by `d.type`.
- Swap `RegistryRef` → `RegistryInterfaceRef` in `query.ts` and
  `registry-permissions-user.ts`.
- `schema/registration.ts`: `WrappedBaseRegistrarRegistration.tokenId` loads
  the domain via the `DomainInterfaceRef` dataloader and reads `domain.node`.

Supporting changes
- `ensdb-sdk` schema: add `domain.node: Hex` (non-null iff ENSv1Domain).
- `ensindexer` ENSv1 `handleNewOwner`: write `node` on domain upsert and set
  parent domain's `subregistryId` to the VirtualRegistry when upserting it
  (so forward traversal + canonical-registries CTE work uniformly with v2).
- `ensnode-sdk`: add `RequiredAndNull<T, K>` helper type (symmetric to
  `RequiredAndNotNull`) for encoding "null in this variant" invariants.

Regenerate pothos generated files (`schema.graphql`, `introspection.ts`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes #205, #1511, #1877.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 22, 2026 16:13
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
admin.ensnode.io Ready Ready Preview, Comment Apr 28, 2026 6:24pm
ensnode.io Ready Ready Preview, Comment Apr 28, 2026 6:24pm
ensrainbow.io Ready Ready Preview, Comment Apr 28, 2026 6:24pm

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 22, 2026

🦋 Changeset detected

Latest commit: 3ddddcf

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 24 packages
Name Type
@ensnode/ensnode-sdk Major
ensapi Major
enssdk Major
@ensnode/ensdb-sdk Major
ensindexer Major
ensadmin Major
ensrainbow Major
fallback-ensapi Major
@namehash/ens-referrals Major
@ensnode/ensnode-react Major
@ensnode/ensrainbow-sdk Major
@ensnode/integration-test-env Major
@namehash/namehash-ui Major
@ensnode/enskit-react-example Patch
enskit Major
@docs/ensnode Major
@docs/ensrainbow Major
enscli Major
ensskills Major
@ensnode/datasources Major
@ensnode/ponder-sdk Major
@ensnode/ponder-subgraph Major
@ensnode/shared-configs Major
@ensnode/ensindexer-perf-testing Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 22, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Consolidates ENSv1/ENSv2 domain and registry models into unified tables and polymorphic GraphQL interfaces; unifies canonical-path traversal and loaders to use namespace-root seeding; makes indexer handlers registry-aware; updates ID constructors, SDK helpers, tests, and docs to the new unified model.

Changes

Cohort / File(s) Summary
Data model & IDs
packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts, packages/enssdk/src/lib/types/ensv2.ts, packages/enssdk/src/lib/ids.ts
Unify v1/v2 domain tables into single domains table with type discriminator; add RegistryType/registry ID brands and new constructors (makeENSv1RegistryId, makeENSv2RegistryId, makeENSv1VirtualRegistryId, makeConcreteRegistryId); ENSv1 domain ID shape becomes ${ENSv1RegistryId}/${node}.
SDK: managed names & root helpers
packages/ensnode-sdk/src/shared/managed-names.ts, packages/ensnode-sdk/src/shared/root-registry.ts, packages/ensnode-sdk/src/index.ts, packages/ensnode-sdk/src/registrars/*
Add getManagedName(namespace, contract) and isNameWrapper; add getRootRegistryId(s) and getENSv1RootRegistryId; remove per-subregistry helpers and associated re-exports/deleted files; re-export managed-names from package root.
Canonical path & find-domains
apps/ensapi/src/omnigraph-api/lib/get-canonical-path.ts, apps/ensapi/src/omnigraph-api/lib/find-domains/canonical-registries-cte.ts, apps/ensapi/src/omnigraph-api/lib/find-domains/layers/...
Merge v1/v2 canonical-path logic into getCanonicalPath(domainId) seeded by all namespace root registry IDs; rewrite canonical-registries CTE to traverse domains via subregistry_id, deduplicate results, and refactor layered find-domains queries to operate over unified domain table.
Context / Loaders
apps/ensapi/src/omnigraph-api/context.ts
Replace v1CanonicalPath/v2CanonicalPath DataLoaders with single canonicalPath DataLoader keyed by DomainId returning `CanonicalPath
GraphQL: Domain & Registry schema
apps/ensapi/src/omnigraph-api/schema/domain.ts, apps/ensapi/src/omnigraph-api/schema/registry.ts
Convert Domain and Registry to loadable interfaces with discriminators (ENSv1Domain/ENSv2Domain, ENSv1Registry/ENSv2Registry/ENSv1VirtualRegistry); add Domain.parent via canonical path; update concrete fields and type guards; switch ENSv2Domain registry refs to interface refs.
GraphQL: Query & resolvers / tests
apps/ensapi/src/omnigraph-api/schema/query.ts, apps/ensapi/src/omnigraph-api/schema/registration.ts, apps/ensapi/src/omnigraph-api/schema/query.integration.test.ts, apps/ensapi/src/omnigraph-api/schema/domain.integration.test.ts
Make Query.root non-null and ENSv2-preferring via getRootRegistryId; Query.registry returns RegistryInterfaceRef? and uses makeConcreteRegistryId fallback; remove dev v1Domains/v2Domains in favor of allDomains; update tests to assert polymorphic types and canonical path behavior; update registration token resolver to load domain and assert ENSv1 runtime type.
Indexer handlers & event wiring
apps/ensindexer/src/plugins/**/handlers/**, apps/ensindexer/src/lib/ensv2/*, apps/ensindexer/src/lib/managed-names.ts
Handlers become registry-aware (derive registry via getManagedName), compute ENSv1 domain IDs as makeENSv1DomainId(registry,node), persist into unified domain and registry tables with type; switch domain/event helper usages to precompute eventId via ensureEvent and pass eventId into ensureDomainEvent/ensureResolverEvent/ensurePermissionsEvent.
Finders / resolution / utilities
apps/ensapi/src/lib/find-resolver.ts, apps/ensapi/src/lib/resolution/forward-resolution.ts, packages/ensnode-sdk/src/shared/protocol-acceleration/is-bridged-resolver.ts
Stop using module-level lazy proxies for legacy datasource lookup; propagate bridged resolver registry (and shadow flag) structures; ensure recursive bridged resolution passes concrete registry identifiers.
Client / cache / tests & tooling
packages/enskit/src/react/omnigraph/_lib/by-id-lookup-resolvers.ts, packages/ensdb-sdk/src/lib/drizzle.test.ts, packages/ensnode-sdk/src/shared/types.ts, AGENTS.md, .changeset/*
Client by-id resolver updated to prefer cached concrete registry typenames; tests adjusted to unified domain table; add RequiredAndNull<T,K> type; update AGENTS testing guidance; add changesets documenting Query.root non-null change and unified domain/registry model.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(220,240,255,0.5)
  participant Client
  end
  rect rgba(200,255,220,0.5)
  participant GraphQL as "Omnigraph API"
  end
  rect rgba(255,235,205,0.5)
  participant Loaders as "Context Loaders\n(canonicalPath)"
  end
  rect rgba(255,215,230,0.5)
  participant DB as "ensIndexer DB\n(domains, registries)"
  end

  Client->>GraphQL: request Domain / Query.root / registry
  GraphQL->>Loaders: load canonicalPath(domainId)
  Loaders->>DB: recursive SQL traversal seeded by getRootRegistryIds(namespace)
  DB-->>Loaders: canonical path (list of DomainId) or null
  Loaders-->>GraphQL: CanonicalPath | Error | null
  GraphQL->>DB: query domain/registry rows via unified `domain`/`registries`
  DB-->>GraphQL: domain/registry records
  GraphQL-->>Client: resolved response (polymorphic types)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 I hopped through rows where types once split,

V1 and V2 now sit in one lit pit.
Paths climb up, roots pick v2 first (if there),
Handlers learn their registry with care.
A tidy hop — hooray! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description only lists three issue numbers without providing a summary, rationale, testing details, or reviewer notes required by the template. Add summary bullets explaining what changed, why this refactoring exists, how it was tested, and any non-obvious implementation notes for reviewers.
Docstring Coverage ⚠️ Warning Docstring coverage is 58.62% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Out of Scope Changes check ❓ Inconclusive While most changes align with the unified model objectives, the scope is extensive and some auxiliary changes (deleted subregistry modules, resolver schema adjustments, protocol-acceleration updates) may warrant clarification on their necessity. Confirm that deleted modules (basenames-subregistry.ts, ethnames-subregistry.ts, lineanames-subregistry.ts) and protocol-acceleration changes are intentional consequences of the unified design and not accidental scope expansion.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main architectural change: unification of v1/v2 domain and registry models into polymorphic structures.
Linked Issues check ✅ Passed The PR comprehensively addresses all three linked issues: #205 (polymorphic domain/registry model unifies conflict handling across registries), #1511 (DomainId terminology replaces Node in APIs), and #1877 (architectural refactor optimizes find-domains traversal).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/ensv1-domain-model

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the ENS data model and API surface to unify ENSv1 + ENSv2 domains into a single polymorphic domain table, introduces a polymorphic Registry model (including ENSv1 virtual registries), and updates the indexer + ENS API to traverse/query the unified namegraph—targeting correctness for multi-registry conflicts (#205) and improving find-domains architecture/perf (#1877) while advancing DomainId terminology (#1511).

Changes:

  • Unifies DB schema from v1Domain/v2Domain into domain (typed by enum), and makes registry polymorphic (ENSv1 concrete / ENSv1 virtual / ENSv2).
  • Changes ENSv1 identifiers to be registry-qualified (ENSv1DomainId = ${ENSv1RegistryId}/${node}) and adds new RegistryId constructors.
  • Updates indexer handlers + ENS API (GraphQL schema, loaders, find-domains layers, canonical path + interpreted-name traversal) to operate on the unified model and expose new GraphQL interfaces/fields (Registry interface, Domain.parent).

Reviewed changes

Copilot reviewed 34 out of 36 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
packages/enssdk/src/omnigraph/generated/schema.graphql Updates generated GraphQL schema: Domain.parent, ENSv1Domain.node, Registry becomes an interface with ENSv1/virtual/v2 implementations.
packages/enssdk/src/omnigraph/generated/introspection.ts Updates generated introspection JSON to match new interfaces/types.
packages/enssdk/src/lib/types/ensv2.ts Introduces branded ENSv1RegistryId/ENSv2RegistryId/ENSv1VirtualRegistryId and changes ENSv1 domain id shape.
packages/enssdk/src/lib/ids.ts Adds makeENSv1RegistryId / makeENSv2RegistryId / makeENSv1VirtualRegistryId; changes makeENSv1DomainId signature to include registry.
packages/ensnode-sdk/src/shared/types.ts Adds RequiredAndNull TS helper used for discriminated polymorphic models.
packages/ensnode-sdk/src/shared/root-registry.ts Adds ENSv1 root registry id helpers and switches ENSv2 root helper to makeENSv2RegistryId.
packages/ensdb-sdk/src/lib/drizzle.test.ts Updates schema cloning tests to reflect table rename (domain).
packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts Core schema refactor: new domain + polymorphic registry, new enums, updated relations/indexes, registryCanonicalDomain.domainId to unified DomainId.
apps/ensindexer/src/plugins/protocol-acceleration/handlers/ThreeDNSToken.ts Updates ENSv1 domain id creation to include registry.
apps/ensindexer/src/plugins/protocol-acceleration/handlers/ENSv1Registry.ts Canonicalizes ENSv1 registry source and updates domain id creation to include registry.
apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.ts Writes ENSv2 domains into unified domain table and inserts registries with polymorphic type.
apps/ensindexer/src/plugins/ensv2/handlers/ensv1/RegistrarController.ts Updates ENSv1 domain id creation to include registry from managed-name group.
apps/ensindexer/src/plugins/ensv2/handlers/ensv1/NameWrapper.ts Updates ENSv1 domain id creation to include registry for wrapper events.
apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts Implements ENSv1 virtual registries + unified domain writes; canonicalizes ENSv1RegistryOld vs new registry.
apps/ensindexer/src/plugins/ensv2/handlers/ensv1/BaseRegistrar.ts Retargets reads/writes to unified domain table and updates ENSv1 id creation.
apps/ensindexer/src/lib/managed-names.ts Expands managed-name mapping to include concrete registry per group; adds ENS root group and shadow registries.
apps/ensindexer/src/lib/managed-names.test.ts Updates tests for new registry return from getManagedName.
apps/ensindexer/src/lib/ensv2/domain-db-helpers.ts Updates effective-owner materialization to write unified domain table.
apps/ensapi/src/omnigraph-api/schema/registry.ts Replaces Registry object with Registry interface + ENSv1/virtual/v2 object types; updates parent/domain connections.
apps/ensapi/src/omnigraph-api/schema/registry-permissions-user.ts Switches to RegistryInterfaceRef.
apps/ensapi/src/omnigraph-api/schema/registration.ts Updates wrapped token id resolution to load ENSv1 domain and use domain.node.
apps/ensapi/src/omnigraph-api/schema/query.ts Retargets v1/v2 domain list queries to unified table; updates Query.registry to resolve concrete registries via DB lookup.
apps/ensapi/src/omnigraph-api/schema/query.integration.test.ts Updates fixtures for ENSv1 id format (registry + node).
apps/ensapi/src/omnigraph-api/schema/domain.ts Reworks Domain loader to unified domain table, adds Domain.parent, and updates ENSv1/ENSv2 type implementations.
apps/ensapi/src/omnigraph-api/lib/get-domain-by-interpreted-name.ts Replaces separate v1/v2 lookup logic with unified forward traversal rooted at v1/v2 root registries.
apps/ensapi/src/omnigraph-api/lib/get-canonical-path.ts Unifies canonical-path resolution into a single reverse-traversal CTE over domain + registryCanonicalDomain.
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-registry.ts Updates docs/semantics for unified registry filtering.
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-parent.ts Updates docs to match unified model (behavior unchanged).
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-name.ts Replaces v1/v2 union traversal with a single traversal over unified domain table.
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-canonical.ts Switches canonical filtering to require membership in canonical registries set.
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/base-domain-set.ts Replaces v1/v2 union base set with a single unified domain-based base set.
apps/ensapi/src/omnigraph-api/lib/find-domains/canonical-registries-cte.ts Replaces ENSv2-only canonical registry traversal with unified traversal rooted at v1 root (+ v2 root if present).
apps/ensapi/src/omnigraph-api/context.ts Consolidates canonical-path loaders into a single canonicalPath loader.
SPEC-domain-model.md Adds/updates design spec for unified polymorphic domain + registry model and migration notes.
.changeset/unified-domain-model.md Declares major bumps and documents breaking changes (schema + id formats + GraphQL).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/ensapi/src/omnigraph-api/lib/find-domains/layers/base-domain-set.ts Outdated
Comment thread apps/ensapi/src/omnigraph-api/lib/get-canonical-path.ts
Comment thread apps/ensapi/src/omnigraph-api/schema/domain.ts
Comment thread apps/ensapi/src/omnigraph-api/schema/registration.ts Outdated
Comment thread apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts
@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io April 22, 2026 17:41 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensnode.io April 22, 2026 17:41 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io April 22, 2026 17:41 Inactive
@shrugs
Copy link
Copy Markdown
Member Author

shrugs commented Apr 22, 2026

@greptile re-review

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 22, 2026

Greptile Summary

This PR unifies ENSv1 and ENSv2 domains/registries into a single polymorphic domain/registry table model, moves ManagedName logic to the SDK with namespace parameterization, and rewrites the namegraph traversal (canonical path, forward resolution, name filtering) to work over the new unified schema.

  • filterByName FQDN regression (P1): the new domainsByLabelHashPath traverses upward via registryCanonicalDomain, but concrete ENSv1Registry rows (TLDs) have no such entry. Any FQDN search with a trailing dot for a 2+ label name (e.g. sub.eth.) fails to reach depth = pathLength and silently returns empty results — a regression from the old parent_id-based traversal.

Confidence Score: 3/5

Not safe to merge — multiple P1 findings across the namegraph traversal, canonical path, and name filtering layers.

One new P1 (FQDN search regression in filterByName) plus several P1s carried over from the previous review round (shadow-registry Domain.name returns only leaf label, getCanonicalPath shadow-registry termination, forward traversal of shadow ENSv1 registries). The refactor is architecturally sound but the traversal correctness issues are widespread enough to warrant careful validation before merge.

apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-name.ts (FQDN search regression), apps/ensapi/src/omnigraph-api/lib/get-canonical-path.ts (shadow registry canonical path), packages/ensnode-sdk/src/shared/root-registry.ts (getRootRegistryIds shadow registry IDs)

Important Files Changed

Filename Overview
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-name.ts Unified domainsByLabelHashPath breaks FQDN searches (trailing-dot 2+ label names) because registryCanonicalDomain is absent for TLD-level concrete registries, preventing the traversal from completing
apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts Unified ENSv1 Registry handler correctly separates concrete vs virtual registry insertion; onConflictDoUpdate omits registryId/node updates (flagged previously)
packages/ensnode-sdk/src/shared/root-registry.ts getRootRegistryIds seeds shadow registries with VirtualRegistry IDs (not concrete), which terminates getCanonicalPath at the shadow root — flagged separately in prior review
packages/ensnode-sdk/src/shared/managed-names.ts ManagedName logic correctly moved to SDK with namespace parameterization; caching and contract-to-registry mapping look correct
apps/ensapi/src/omnigraph-api/lib/find-domains/canonical-registries-cte.ts CTE now correctly seeds from all root registry IDs and filters NULL subregistry rows early in the recursive step
apps/ensapi/src/omnigraph-api/lib/get-canonical-path.ts Shadow registry domains return only the leaf label in Domain.name because the upward traversal terminates immediately at the VirtualRegistry root (flagged in prior review)
apps/ensapi/src/omnigraph-api/lib/get-domain-by-interpreted-name.ts Bridged resolver traversal logic looks structurally sound; shadow registry path is correctly passed in full to the shadow concrete registry
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-canonical.ts Switched from LEFT JOIN to INNER JOIN against canonical_registries CTE; correctness depends on subregistryId being properly set (previously flagged)
apps/ensapi/src/omnigraph-api/schema/domain.ts Polymorphic ENSv1Domain/ENSv2Domain GraphQL types correctly implemented; Domain.name uses canonical path loader
apps/ensapi/src/omnigraph-api/schema/registry.ts New polymorphic Registry GraphQL types (ENSv1Registry, ENSv1VirtualRegistry, ENSv2Registry) look correct

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["ENSv1 NewOwner\n(parentNode = ENS_ROOT_NODE)"] -->|isTLD = true| B["Insert ENSv1Registry\n(concrete, no RCD entry)"]
    A -->|isTLD = false| C["Insert ENSv1VirtualRegistry\n+ registryCanonicalDomain entry"]
    C --> D["Update parent domain\nsubregistryId = virtualRegistryId"]

    E["filterByName\ndomainsByLabelHashPath"] -->|pathLength = 1| F["Traverse via RCD\ndepth=1 → TLD domain ✓"]
    E -->|pathLength ≥ 2 with TLD in concrete| G["depth=N step: TLD domain\nregistryId = concrete ENSv1Registry\nNO registryCanonicalDomain → JOIN fails\n❌ Returns empty"]

    H["getCanonicalPath\nfoo.base.eth"] --> I["registryId = VirtualRegistry for base.eth\n= rootRegistryIds entry → stop immediately"]
    I --> J["Path = [foo.base.eth.id] only\nDomain.name = 'foo' not 'foo.base.eth' ❌"]

    K["getRootRegistryIds"] --> L["ENSv1 Root concrete registry ✓"]
    K --> M["Basenames/Lineanames:\nmakeENSv1VirtualRegistry(shadowRegistry, managedNode)\n← terminates traversal at shadow root,\n  not at concrete registry"]
Loading

Reviews (16): Last reviewed commit: "fix: support ensv1resolver fallback" | Re-trigger Greptile

Comment thread apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts Outdated
Comment thread apps/ensapi/src/omnigraph-api/schema/registration.ts
Comment thread apps/ensapi/src/omnigraph-api/schema/query.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR unifies ENSv1/ENSv2 “Domain” and “Registry” modeling into a single polymorphic namegraph across the indexer DB schema and Omnigraph GraphQL API, enabling consistent canonical traversal and handling of shadow registries/subregistries while advancing the DomainId terminology/ID-shape refactor.

Changes:

  • Unifies indexed storage into polymorphic domains + registries tables (incl. ENSv1 virtual registries) and updates canonical traversal (forward + reverse) to operate on the unified graph.
  • Updates enssdk ID/type shapes (notably CAIP-shaped ENSv1DomainId and polymorphic RegistryId) and adds managed-name/root-registry helpers in @ensnode/ensnode-sdk.
  • Refactors ensindexer handlers + ensapi schema/resolvers + enskit cache resolvers/tests to use the unified types and new canonical-path logic.

Reviewed changes

Copilot reviewed 47 out of 49 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/enssdk/src/omnigraph/generated/schema.graphql Updates Omnigraph schema: Domain gains parent, Registry becomes interface w/ v1/v1-virtual/v2 types, adds allDomains, makes root non-null.
packages/enssdk/src/omnigraph/generated/introspection.ts Regenerates GraphQL introspection to match new schema/interface polymorphism.
packages/enssdk/src/lib/types/ensv2.ts Introduces polymorphic RegistryId types and CAIP-shaped ENSv1DomainId/virtual-registry IDs.
packages/enssdk/src/lib/ids.ts Adds constructors for new RegistryId variants and updates ENSv1 domain ID construction.
packages/ensnode-sdk/src/shared/types.ts Adds RequiredAndNull<T, K> utility type.
packages/ensnode-sdk/src/shared/root-registry.ts Adds getRootRegistryId/getRootRegistryIds and switches to explicit v1/v2 registry ID constructors.
packages/ensnode-sdk/src/shared/managed-names.ts New managed-name registry-aware helper (maps contracts → managed name/node/registry).
packages/ensnode-sdk/src/registrars/lineanames-subregistry.ts Removes legacy Lineanames subregistry helper (superseded by managed-name logic).
packages/ensnode-sdk/src/registrars/index.ts Removes exports for deleted registrar-specific subregistry helpers.
packages/ensnode-sdk/src/registrars/ethnames-subregistry.ts Removes legacy Ethnames subregistry helper (superseded by managed-name logic).
packages/ensnode-sdk/src/registrars/basenames-subregistry.ts Removes legacy Basenames subregistry helper (superseded by managed-name logic).
packages/ensnode-sdk/src/index.ts Exposes new shared managed-name utilities from the SDK package entrypoint.
packages/enskit/src/react/omnigraph/_lib/by-id-lookup-resolvers.ts Updates graphcache resolvers to resolve Registry/Domain interface entities (incl. virtual registries).
packages/ensdb-sdk/src/lib/drizzle.test.ts Updates schema cloning tests to expect unified domain table.
packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts Refactors abstract schema: unified domain table, polymorphic registry, and updated relations/indexes.
apps/ensindexer/src/plugins/protocol-acceleration/handlers/ThreeDNSToken.ts Updates ENSv1 domain ID creation to include registry context.
apps/ensindexer/src/plugins/protocol-acceleration/handlers/ENSv1Registry.ts Canonicalizes ENSv1 registry and updates resolver-domain relationships for CAIP-shaped ENSv1 domain IDs.
apps/ensindexer/src/plugins/ensv2/handlers/shared/Resolver.ts Refactors resolver event indexing to reuse ensureEvent and pass eventId.
apps/ensindexer/src/plugins/ensv2/handlers/ensv2/EnhancedAccessControl.ts Refactors permissions event indexing to reuse ensureEvent and pass eventId.
apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ETHRegistrar.ts Refactors domain/renewal event indexing to reuse ensureEvent and pass eventId.
apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.ts Inserts registry/domain rows into unified tables and normalizes event indexing via ensureEvent.
apps/ensindexer/src/plugins/ensv2/handlers/ensv1/RegistrarController.ts Updates managed-name usage and ENSv1 domain ID creation; refactors event indexing via ensureEvent.
apps/ensindexer/src/plugins/ensv2/handlers/ensv1/NameWrapper.ts Updates ENSv1 domain ID creation and refactors event indexing via ensureEvent.
apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts Implements ENSv1 virtual registries + unified domain upserts and refactors history events via ensureEvent.
apps/ensindexer/src/plugins/ensv2/handlers/ensv1/BaseRegistrar.ts Updates managed-name usage and ENSv1 domain ID creation; refactors event indexing via ensureEvent.
apps/ensindexer/src/lib/managed-names.ts Replaces local managed-name implementation with thin wrappers around SDK helpers.
apps/ensindexer/src/lib/managed-names.test.ts Updates managed-name tests to assert registry association and to use more flexible matchers.
apps/ensindexer/src/lib/ensv2/event-db-helpers.ts Changes helpers to accept eventId (decoupling edge inserts from event creation).
apps/ensindexer/src/lib/ensv2/domain-db-helpers.ts Updates ENSv1 effective-owner materialization to target unified domain table.
apps/ensapi/src/omnigraph-api/schema/registry.ts Implements Registry as a loadable interface + concrete types; updates parents/domains querying to unified domain table.
apps/ensapi/src/omnigraph-api/schema/registry-permissions-user.ts Updates registry field type to use the new Registry interface ref.
apps/ensapi/src/omnigraph-api/schema/registration.ts Updates wrapped-registration tokenId resolution to work with new ENSv1 domain ID shape.
apps/ensapi/src/omnigraph-api/schema/query.ts Replaces v1/v2 domain dev queries with allDomains; makes root non-null via getRootRegistryId; updates registry lookup semantics.
apps/ensapi/src/omnigraph-api/schema/query.integration.test.ts Updates tests for non-null root, registry polymorphism, ENSv1 node exposure, and canonical filtering.
apps/ensapi/src/omnigraph-api/schema/domain.ts Refactors to unified Domain interface loading + canonical-path loader; adds parent and ENSv1 node.
apps/ensapi/src/omnigraph-api/schema/domain.integration.test.ts Adds integration coverage for canonical path semantics (leaf→root) and alias collapsing.
apps/ensapi/src/omnigraph-api/lib/get-domain-by-interpreted-name.ts Refactors forward traversal to operate from all configured root registries over the unified domain table.
apps/ensapi/src/omnigraph-api/lib/get-canonical-path.ts Collapses v1/v2 canonical path logic into unified reverse traversal over the namegraph.
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-registry.ts Updates docs/semantics for unified registry filtering.
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-parent.ts Updates docs to reflect unified parent derivation.
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-name.ts Replaces v1/v2 union traversal with unified upward traversal via registry-canonical edges.
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-canonical.ts Switches canonical filtering to inner-join against canonical registry set for unified domains.
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/base-domain-set.ts Removes v1/v2 unions; constructs a single base-domain set from unified domain table.
apps/ensapi/src/omnigraph-api/lib/find-domains/canonical-registries-cte.ts Builds canonical registry set by forward traversal from all configured roots over unified domains.
apps/ensapi/src/omnigraph-api/context.ts Consolidates canonical-path DataLoader into one loader over unified DomainId.
apps/ensapi/src/lib/name-tokens/get-indexed-subregistries.ts Simplifies indexed subregistries discovery using SDK managed-name helper + datasource contracts.
AGENTS.md Updates contributor guidance (assertion patterns + schema generation reminders).
.changeset/unified-domain-model.md Announces unified domain/registry model and breaking ID/schema changes.
.changeset/query-root-nonnull.md Announces Query.root becoming non-null with v2-preferred fallback behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/enssdk/src/lib/ids.ts Outdated
Comment thread packages/ensnode-sdk/src/shared/managed-names.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/ensindexer/src/plugins/ensv2/handlers/ensv1/BaseRegistrar.ts (1)

245-250: ⚠️ Potential issue | 🟠 Major

Reuse the precomputed eventId in insertLatestRenewal.

Line 249 re-calls ensureEvent(context, event) even though eventId is already computed at Line 245. This adds an unnecessary DB write/read in a hot path.

♻️ Proposed fix
       await insertLatestRenewal(context, registration, {
         domainId,
         duration,
-        eventId: await ensureEvent(context, event),
+        eventId,
         // NOTE: no pricing information from BaseRegistrar#NameRenewed. in ENSv1, this info is
         // indexed from the Registrar Controllers, see apps/ensindexer/src/plugins/ensv2/handlers/ensv1/RegistrarController.ts
       });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensindexer/src/plugins/ensv2/handlers/ensv1/BaseRegistrar.ts` around
lines 245 - 250, The code recomputes the eventId by calling ensureEvent(context,
event) twice; replace the second call with the already-computed eventId variable
when calling insertLatestRenewal to avoid the duplicate DB work. In
BaseRegistrar.ts, update the insertLatestRenewal call to pass the local eventId
(from const eventId = await ensureEvent(context, event)) instead of awaiting
ensureEvent(context, event) again so registration handling uses the precomputed
eventId.
♻️ Duplicate comments (2)
packages/ensnode-sdk/src/shared/managed-names.ts (1)

204-219: 🧹 Nitpick | 🔵 Trivial

Avoid duplicating the wrapper contract lookup here.

This still hard-codes the same NameWrapper checks that getContractsByManagedName already builds, so a future wrapper addition can drift out of sync. Reusing the managed-name group data or a shared cached wrapper set would keep the contract list in one place.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ensnode-sdk/src/shared/managed-names.ts` around lines 204 - 219, The
isNameWrapper function duplicates lookup logic for "NameWrapper"; replace the
manual checks with a single call to the existing managed-name resolver (e.g.,
getContractsByManagedName or the function that builds the managed-name group for
wrappers) and test membership against that returned contract list (optionally
caching the resulting Set for performance). Specifically, update isNameWrapper
to call the shared function that returns the NameWrapper contracts for the given
namespace (instead of calling getDatasourceContract/maybeGetDatasourceContract
twice), then use accountIdEqual against the items in that list or a cached Set
to determine and return true/false.
apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts (1)

98-102: ⚠️ Potential issue | 🟠 Major

Parent subregistryId is set via UPDATE-only and can be dropped silently.

If the parent domain row is missing when this event is processed, this UPDATE no-ops and the parent never gets linked to its subregistry, which breaks forward traversal until repaired.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts` around
lines 98 - 102, The UPDATE against ensIndexerSchema.domain using
context.ensDb.update({ id: parentDomainId }).set({ subregistryId:
parentRegistryId }) can silently no-op if the parent domain row doesn't exist;
change this to an upsert or existence-checked flow so the parent row is created
when missing and then linked: either perform a select for parentDomainId and if
absent insert a new domain row with id = parentDomainId and subregistryId =
parentRegistryId, or use your DB client's upsert/insert-on-conflict/merge
operation to set subregistryId atomically; ensure you modify the code around
context.ensDb.update(...) (referencing ensIndexerSchema.domain, parentDomainId,
parentRegistryId) to guarantee the parent domain is created when missing before
or during setting subregistryId.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/ensapi/src/omnigraph-api/lib/find-domains/canonical-registries-cte.ts`:
- Around line 23-31: The JSDoc is out of date: update the comment describing the
recursive CTE to reflect that getRootRegistryIds(config.namespace) returns all
configured root registries, not just concrete ENSv1Registries and the ENSv2
Root; explicitly mention that it also includes the Basenames and Lineanames
virtual registries when configured so the description of which roots the
traversal starts from is accurate (update the JSDoc block above the canonical
registries CTE accordingly).

In `@apps/ensapi/src/omnigraph-api/lib/get-domain-by-interpreted-name.ts`:
- Around line 127-152: The exact leaf domain (deepest.domainId) can be lost if
an ancestor bridged resolver triggers recursion that returns null; to fix,
preserve the exact leaf id before recursing and use it as a fallback: when
computing deepestResolver and bridgesTo, store const fallbackId =
deepest.domainId (or similar), call resolveCanonicalDomainId(targetRegistryId,
targetPath, depth + 1) into a variable, and if that recursive call returns
null/undefined, return fallbackId instead of propagating null; update references
in this block around hasResolver, deepest, deepestResolver,
makeConcreteRegistryId and resolveCanonicalDomainId so the original exact match
is returned when recursion fails.

In `@apps/ensindexer/src/plugins/ensv2/handlers/ensv1/NameWrapper.ts`:
- Around line 383-385: The ExpiryExtended handler in NameWrapper calls
ensureEvent twice; replace the second redundant call with reuse of the
already-created eventId variable: after awaiting const eventId = await
ensureEvent(context, event) keep using that eventId when calling
ensureDomainEvent and any subsequent logic (avoid calling ensureEvent(context,
event) again), and apply the same fix to the second occurrence around the other
block (the sequence that currently re-computes eventId between lines like
407–411) so the code only creates the event once and reuses eventId for
ensureDomainEvent and downstream operations.

---

Outside diff comments:
In `@apps/ensindexer/src/plugins/ensv2/handlers/ensv1/BaseRegistrar.ts`:
- Around line 245-250: The code recomputes the eventId by calling
ensureEvent(context, event) twice; replace the second call with the
already-computed eventId variable when calling insertLatestRenewal to avoid the
duplicate DB work. In BaseRegistrar.ts, update the insertLatestRenewal call to
pass the local eventId (from const eventId = await ensureEvent(context, event))
instead of awaiting ensureEvent(context, event) again so registration handling
uses the precomputed eventId.

---

Duplicate comments:
In `@apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts`:
- Around line 98-102: The UPDATE against ensIndexerSchema.domain using
context.ensDb.update({ id: parentDomainId }).set({ subregistryId:
parentRegistryId }) can silently no-op if the parent domain row doesn't exist;
change this to an upsert or existence-checked flow so the parent row is created
when missing and then linked: either perform a select for parentDomainId and if
absent insert a new domain row with id = parentDomainId and subregistryId =
parentRegistryId, or use your DB client's upsert/insert-on-conflict/merge
operation to set subregistryId atomically; ensure you modify the code around
context.ensDb.update(...) (referencing ensIndexerSchema.domain, parentDomainId,
parentRegistryId) to guarantee the parent domain is created when missing before
or during setting subregistryId.

In `@packages/ensnode-sdk/src/shared/managed-names.ts`:
- Around line 204-219: The isNameWrapper function duplicates lookup logic for
"NameWrapper"; replace the manual checks with a single call to the existing
managed-name resolver (e.g., getContractsByManagedName or the function that
builds the managed-name group for wrappers) and test membership against that
returned contract list (optionally caching the resulting Set for performance).
Specifically, update isNameWrapper to call the shared function that returns the
NameWrapper contracts for the given namespace (instead of calling
getDatasourceContract/maybeGetDatasourceContract twice), then use accountIdEqual
against the items in that list or a cached Set to determine and return
true/false.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 41345e0a-1b3a-4197-8be0-af4676362d8f

📥 Commits

Reviewing files that changed from the base of the PR and between ba61cee and e9fb641.

⛔ Files ignored due to path filters (1)
  • packages/enssdk/src/omnigraph/generated/schema.graphql is excluded by !**/generated/**
📒 Files selected for processing (19)
  • .changeset/ensnode-sdk-managed-name-api.md
  • apps/ensapi/src/lib/protocol-acceleration/find-resolver.ts
  • apps/ensapi/src/lib/resolution/forward-resolution.ts
  • apps/ensapi/src/omnigraph-api/lib/find-domains/canonical-registries-cte.ts
  • apps/ensapi/src/omnigraph-api/lib/get-domain-by-interpreted-name.ts
  • apps/ensapi/src/omnigraph-api/schema/resolver.ts
  • apps/ensindexer/src/lib/ensv2/event-db-helpers.ts
  • apps/ensindexer/src/lib/managed-names.test.ts
  • apps/ensindexer/src/plugins/ensv2/handlers/ensv1/BaseRegistrar.ts
  • apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts
  • apps/ensindexer/src/plugins/ensv2/handlers/ensv1/NameWrapper.ts
  • apps/ensindexer/src/plugins/ensv2/handlers/ensv1/RegistrarController.ts
  • apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.ts
  • apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ETHRegistrar.ts
  • apps/ensindexer/src/plugins/ensv2/handlers/ensv2/EnhancedAccessControl.ts
  • apps/ensindexer/src/plugins/ensv2/handlers/shared/Resolver.ts
  • packages/enskit/src/react/omnigraph/_lib/by-id-lookup-resolvers.ts
  • packages/ensnode-sdk/src/shared/managed-names.ts
  • packages/ensnode-sdk/src/shared/protocol-acceleration/is-bridged-resolver.ts

Comment thread apps/ensapi/src/omnigraph-api/lib/get-domain-by-interpreted-name.ts
Comment thread apps/ensindexer/src/plugins/ensv2/handlers/ensv1/NameWrapper.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors ENSNode’s domain/registry modeling to a unified, polymorphic “namegraph” across ENSv1 (including shadow registries) and ENSv2, aligning indexing, DB schema, and the Omnigraph GraphQL API around a single domain table and a polymorphic registry interface.

Changes:

  • Unifies v1Domain + v2Domain into a single domain table (discriminated by DomainType) and makes Registry polymorphic (ENSv1 concrete, ENSv1 virtual, ENSv2).
  • Updates ID formats/builders (notably ENSv1DomainId now includes the concrete ENSv1 registry; introduces ENSv1/ENSv2/virtual registry ID constructors).
  • Updates Omnigraph GraphQL schema + resolvers (new Domain.parent, unified domain querying, registry polymorphism) and updates indexer handlers/tests accordingly.

Reviewed changes

Copilot reviewed 52 out of 54 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/enssdk/src/omnigraph/generated/schema.graphql Updates Omnigraph SDL for Domain.parent, polymorphic Registry, allDomains, etc.
packages/enssdk/src/omnigraph/generated/introspection.ts Regenerates introspection JSON to match updated schema types/interfaces.
packages/enssdk/src/lib/types/ensv2.ts Splits RegistryId into ENSv1/ENSv2/virtual variants; changes ENSv1 ID branding/shape.
packages/enssdk/src/lib/ids.ts Adds new registry/domain ID constructors; changes ENSv1 domain ID construction to include registry.
packages/ensnode-sdk/src/shared/types.ts Adds RequiredAndNull helper type used for polymorphic narrowing.
packages/ensnode-sdk/src/shared/root-registry.ts Adds getRootRegistryId/getRootRegistryIds/getENSv1RootRegistryId; refactors imports.
packages/ensnode-sdk/src/shared/protocol-acceleration/is-bridged-resolver.ts Changes bridged-resolver detection return type to include { registry, shadow }.
packages/ensnode-sdk/src/shared/managed-names.ts Introduces centralized managed-name mapping (name/node/concrete ENSv1 registry) + memoization.
packages/ensnode-sdk/src/registrars/lineanames-subregistry.ts Removes per-subregistry helper (replaced by managed-name helpers).
packages/ensnode-sdk/src/registrars/index.ts Stops exporting removed subregistry helper modules.
packages/ensnode-sdk/src/registrars/ethnames-subregistry.ts Removes per-subregistry helper (replaced by managed-name helpers).
packages/ensnode-sdk/src/registrars/basenames-subregistry.ts Removes per-subregistry helper (replaced by managed-name helpers).
packages/ensnode-sdk/src/index.ts Re-exports new managed-name helpers.
packages/enskit/src/react/omnigraph/_lib/by-id-lookup-resolvers.ts Updates registry entity resolution for polymorphic registry types and concrete-vs-virtual IDs.
packages/ensdb-sdk/src/lib/drizzle.test.ts Updates schema/table assertions to reflect unified domain table.
packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts Implements unified domains table, polymorphic registries, and updated relations/indexing model.
apps/ensindexer/src/plugins/protocol-acceleration/handlers/ThreeDNSToken.ts Updates ENSv1 domain ID construction to include concrete registry.
apps/ensindexer/src/plugins/protocol-acceleration/handlers/ENSv1Registry.ts Canonicalizes ENSv1 registry selection via managed names; updates ENSv1 domain IDs.
apps/ensindexer/src/plugins/ensv2/handlers/shared/Resolver.ts Normalizes event insertion (ensureEvent) and updates ensureResolverEvent signature usage.
apps/ensindexer/src/plugins/ensv2/handlers/ensv2/EnhancedAccessControl.ts Normalizes event insertion (ensureEvent) and updates ensurePermissionsEvent usage.
apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ETHRegistrar.ts Normalizes event insertion and updates ensureDomainEvent usage to accept eventId.
apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.ts Updates registry/domain inserts to new polymorphic tables + ID constructors; normalizes event usage.
apps/ensindexer/src/plugins/ensv2/handlers/ensv1/RegistrarController.ts Uses managed-name registry for ENSv1 domain IDs; normalizes domain-event insertion.
apps/ensindexer/src/plugins/ensv2/handlers/ensv1/NameWrapper.ts Uses managed-name registry for ENSv1 domain IDs; normalizes domain-event insertion.
apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts Rewrites ENSv1 registry indexing into unified domain/registry tables incl. ENSv1 virtual registries.
apps/ensindexer/src/plugins/ensv2/handlers/ensv1/BaseRegistrar.ts Uses managed-name registry for ENSv1 domain IDs; normalizes event insertion and domain lookups.
apps/ensindexer/src/lib/managed-names.ts Replaces indexer-local mapping with thin wrappers over SDK managed-name helpers.
apps/ensindexer/src/lib/managed-names.test.ts Updates tests to validate new managed-name result shape and memoization behavior.
apps/ensindexer/src/lib/ensv2/event-db-helpers.ts Changes ensure*Event helpers to accept a precomputed eventId.
apps/ensindexer/src/lib/ensv2/domain-db-helpers.ts Updates ENSv1 owner materialization to target unified domain table.
apps/ensapi/src/omnigraph-api/schema/resolver.ts Updates Resolver.bridged to return bridged target registry from new bridged-resolver API.
apps/ensapi/src/omnigraph-api/schema/registry.ts Implements polymorphic Registry as a GraphQL interface with v1/v1-virtual/v2 concrete types.
apps/ensapi/src/omnigraph-api/schema/registry-permissions-user.ts Updates registry reference type and ID constructor usage in permissions-user view.
apps/ensapi/src/omnigraph-api/schema/registration.ts Fixes wrapped-registration tokenId derivation by loading domain and asserting ENSv1 domain type.
apps/ensapi/src/omnigraph-api/schema/query.ts Replaces v1Domains/v2Domains test fields with unified allDomains; makes root non-null.
apps/ensapi/src/omnigraph-api/schema/query.integration.test.ts Updates tests for non-null root + registry polymorphism + ENSv1 domain node exposure.
apps/ensapi/src/omnigraph-api/schema/domain.ts Reworks Domain as loadable interface over unified domain table; adds Domain.parent and ENSv1 node.
apps/ensapi/src/omnigraph-api/schema/domain.integration.test.ts Adds canonical-path integration tests for leaf→root path and alias collapsing.
apps/ensapi/src/omnigraph-api/lib/get-domain-by-interpreted-name.ts Rewrites name→domain lookup using canonical traversal + bridged-resolver recursion.
apps/ensapi/src/omnigraph-api/lib/get-canonical-path.ts Replaces v1/v2 canonical-path helpers with unified reverse traversal using root registries list.
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-registry.ts Updates comments/behavior to reflect unified domain model.
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-parent.ts Updates comments to match unified parent derivation logic.
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-name.ts Unifies v1/v2 name filtering into a single recursive query over domain + registryCanonicalDomain.
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-canonical.ts Simplifies canonical filtering using canonical registries CTE + unified registry IDs.
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/base-domain-set.ts Replaces unioned v1/v2 base sets with a single base set over unified domain table.
apps/ensapi/src/omnigraph-api/lib/find-domains/canonical-registries-cte.ts Rebuilds canonical registries set by forward-walking domain.subregistryId from all namespace roots.
apps/ensapi/src/omnigraph-api/context.ts Replaces v1/v2 canonical-path loaders with a unified canonicalPath loader.
apps/ensapi/src/lib/resolution/forward-resolution.ts Adapts bridged-resolver return type change (bridgesTo.registry).
apps/ensapi/src/lib/protocol-acceleration/find-resolver.ts Removes lazy proxy; resolves ENSv1RegistryOld inline during indexed resolver lookup.
apps/ensapi/src/lib/name-tokens/get-indexed-subregistries.ts Switches to managed-name helpers to compute subregistry managed nodes.
AGENTS.md Updates contributor guidance for test assertion style + schema generation commands.
.changeset/unified-domain-model.md Adds release note for unified polymorphic domain/registry model and breaking ID changes.
.changeset/query-root-nonnull.md Adds release note for Query.root now being non-null with fallback behavior.
.changeset/ensnode-sdk-managed-name-api.md Adds release note for new managed-name/root-registry helpers and removed registrar helpers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .changeset/unified-domain-model.md Outdated
Comment thread packages/ensnode-sdk/src/shared/root-registry.ts
Comment thread apps/ensapi/src/omnigraph-api/lib/get-domain-by-interpreted-name.ts
Comment thread apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts
@shrugs
Copy link
Copy Markdown
Member Author

shrugs commented Apr 28, 2026

@greptile review

@tk-o tk-o mentioned this pull request Apr 28, 2026
2 tasks
Copy link
Copy Markdown
Member

@lightwalker-eth lightwalker-eth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

Comment thread packages/ensnode-sdk/src/shared/root-registry.ts
@shrugs
Copy link
Copy Markdown
Member Author

shrugs commented Apr 28, 2026

@greptile review

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors ENSNode’s data model and Omnigraph API to treat ENSv1/ENSv2 domains and registries as a unified polymorphic namegraph, addressing cross-registry conflicts and improving canonical traversal semantics.

Changes:

  • Unifies v1Domain + v2Domain into a single polymorphic domain table and introduces polymorphic Registry (ENSv1 concrete, ENSv1 virtual, ENSv2) end-to-end (DB → indexer → API → SDK).
  • Updates ID formats/helpers (notably ENSv1DomainId becomes ${ENSv1RegistryId}/${node}) and centralizes “managed name” + root-registry helpers in @ensnode/ensnode-sdk.
  • Updates GraphQL schema to expose Domain.parent, new Registry interface implementations, and consolidates dev-only domain listing via allDomains.

Reviewed changes

Copilot reviewed 52 out of 54 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/enssdk/src/omnigraph/generated/schema.graphql Updates generated Omnigraph schema for polymorphic Registry, Domain.parent, and allDomains.
packages/enssdk/src/omnigraph/generated/introspection.ts Updates generated introspection JSON to match the new schema shape/types.
packages/enssdk/src/lib/types/ensv2.ts Refactors SDK type brands for polymorphic RegistryId and CAIP-shaped ENSv1DomainId.
packages/enssdk/src/lib/ids.ts Adds new ID constructors (makeENSv1RegistryId, makeENSv1VirtualRegistryId, makeConcreteRegistryId, etc.).
packages/ensnode-sdk/src/shared/types.ts Adds RequiredAndNull<> helper type used for discriminated polymorphic models.
packages/ensnode-sdk/src/shared/root-registry.ts Adds getRootRegistryId(s) and ENSv1/v2 root registry ID helpers.
packages/ensnode-sdk/src/shared/protocol-acceleration/is-bridged-resolver.ts Changes bridged-resolver detection to return { registry, shadow }.
packages/ensnode-sdk/src/shared/managed-names.ts Introduces centralized managed-name resolution (name/node/registry) with memoization.
packages/ensnode-sdk/src/registrars/lineanames-subregistry.ts Removes legacy Lineanames subregistry helper in favor of managed-name API.
packages/ensnode-sdk/src/registrars/ethnames-subregistry.ts Removes legacy Ethnames subregistry helper in favor of managed-name API.
packages/ensnode-sdk/src/registrars/basenames-subregistry.ts Removes legacy Basenames subregistry helper in favor of managed-name API.
packages/ensnode-sdk/src/registrars/index.ts Stops exporting removed registrar-subregistry helpers.
packages/ensnode-sdk/src/index.ts Exports the new managed-name helpers.
packages/enskit/src/react/omnigraph/_lib/by-id-lookup-resolvers.ts Updates URQL graphcache by-id resolvers for polymorphic Registry types.
packages/ensdb-sdk/src/lib/drizzle.test.ts Updates tests to reflect domain table rename/unification.
packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts Unifies DB schema: adds polymorphic domain + registry typing and relations.
apps/ensindexer/src/plugins/protocol-acceleration/handlers/ThreeDNSToken.ts Updates ENSv1 domain ID construction to include registry context.
apps/ensindexer/src/plugins/protocol-acceleration/handlers/ENSv1Registry.ts Canonicalizes ENSv1 registry for resolver relations; uses new ENSv1 domain IDs.
apps/ensindexer/src/plugins/ensv2/handlers/shared/Resolver.ts Refactors event linking to reuse ensureEvent() and pass eventId through.
apps/ensindexer/src/plugins/ensv2/handlers/ensv2/EnhancedAccessControl.ts Refactors permissions event linking to use ensureEvent() + new helper signatures.
apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ETHRegistrar.ts Refactors event creation/linking to avoid redundant ensureEvent() calls.
apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.ts Updates registry/domain upserts for polymorphic tables and registry typing.
apps/ensindexer/src/plugins/ensv2/handlers/ensv1/RegistrarController.ts Uses managed-name registry context for ENSv1 domain IDs and new event helper signatures.
apps/ensindexer/src/plugins/ensv2/handlers/ensv1/NameWrapper.ts Uses managed-name registry context for ENSv1 domain IDs; refactors event handling.
apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts Builds ENSv1 virtual registries + unified domain rows; maintains canonical-domain links.
apps/ensindexer/src/plugins/ensv2/handlers/ensv1/BaseRegistrar.ts Uses managed-name registry context for ENSv1 domain IDs; refactors event linking.
apps/ensindexer/src/lib/managed-names.ts Converts to thin wrappers around SDK managed-name helpers bound to config.namespace.
apps/ensindexer/src/lib/managed-names.test.ts Updates tests for new managed-name return shape (includes registry) and memoization semantics.
apps/ensindexer/src/lib/ensv2/event-db-helpers.ts Changes helper signatures to accept eventId (callers ensure event once).
apps/ensindexer/src/lib/ensv2/domain-db-helpers.ts Updates effective-owner materialization to write into unified domain table.
apps/ensapi/src/omnigraph-api/schema/resolver.ts Updates Resolver.bridged semantics to use new bridged-resolver return shape.
apps/ensapi/src/omnigraph-api/schema/registry.ts Implements polymorphic Registry GraphQL interface + concrete types.
apps/ensapi/src/omnigraph-api/schema/registry-permissions-user.ts Updates registry resolution to target polymorphic Registry interface.
apps/ensapi/src/omnigraph-api/schema/registration.ts Fixes Wrapped BaseRegistrar tokenId resolution under new ENSv1DomainId format.
apps/ensapi/src/omnigraph-api/schema/query.ts Adds dev allDomains, updates registry and makes root non-null with v2→v1 preference.
apps/ensapi/src/omnigraph-api/schema/query.integration.test.ts Updates integration tests for new root/registry polymorphism + ENSv1 node exposure.
apps/ensapi/src/omnigraph-api/schema/domain.ts Unifies domain loading, adds parent, updates path to leaf→root canonical ordering.
apps/ensapi/src/omnigraph-api/schema/domain.integration.test.ts Adds integration coverage for canonical Domain.path and alias collapsing.
apps/ensapi/src/omnigraph-api/lib/get-domain-by-interpreted-name.ts Refactors forward namegraph lookup with DRR join + bridged-resolver recursion.
apps/ensapi/src/omnigraph-api/lib/get-canonical-path.ts Unifies canonical-path traversal using registry-canonical-domain edges and multiple roots.
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-registry.ts Updates docs/behavior to reflect unified registry filtering across domain types.
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-parent.ts Updates docs/behavior to reflect unified parent derivation via registry canonical traversal.
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-name.ts Unifies name search traversal to operate on domain table only (no v1/v2 union).
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-canonical.ts Updates canonical filter to rely on canonical-registries CTE with unified registries.
apps/ensapi/src/omnigraph-api/lib/find-domains/layers/base-domain-set.ts Rebuilds the base domain set over unified domain table with derived parentId.
apps/ensapi/src/omnigraph-api/lib/find-domains/canonical-registries-cte.ts Updates canonical-registry traversal to start from all namespace root registries.
apps/ensapi/src/omnigraph-api/context.ts Replaces v1/v2 canonical-path loaders with a unified canonical-path loader.
apps/ensapi/src/lib/resolution/forward-resolution.ts Updates accelerated bridged-resolver handling for new { registry, shadow } return.
apps/ensapi/src/lib/protocol-acceleration/find-resolver.ts Removes lazy proxy for ENSv1RegistryOld and resolves it at query time.
apps/ensapi/src/lib/name-tokens/get-indexed-subregistries.ts Replaces per-subregistry helpers with managed-name + datasource-based discovery.
AGENTS.md Updates contributor guidance around test assertion patterns and generation steps.
.changeset/unified-domain-model.md Documents breaking schema/ID changes and new GraphQL polymorphism.
.changeset/query-root-nonnull.md Documents Query.root becoming non-null with v2→v1 preference.
.changeset/ensnode-sdk-managed-name-api.md Documents removal of old subregistry helpers and new managed-name/root helpers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/ensapi/src/omnigraph-api/lib/get-domain-by-interpreted-name.ts
Comment thread apps/ensapi/src/omnigraph-api/lib/get-domain-by-interpreted-name.ts
@github-actions github-actions Bot mentioned this pull request Apr 27, 2026
Comment on lines 64 to +103
sql`(
WITH RECURSIVE upward_check AS (
-- Base case: find the deepest children (leaves of the concrete path)
-- Base case: find the deepest children (leaves of the concrete path) and walk one step
-- up via registryCanonicalDomain. The parent.subregistry_id = d.registry_id clause
-- performs edge authentication.
SELECT
d.id AS leaf_id,
d.parent_id AS current_id,
parent.id AS current_id,
1 AS depth
FROM ${ensIndexerSchema.v1Domain} d
WHERE d.label_hash = (${rawLabelHashPathArray})[${pathLength}]

UNION ALL

-- Recursive step: traverse UP, verifying each ancestor's labelHash
SELECT
upward_check.leaf_id,
pd.parent_id AS current_id,
upward_check.depth + 1
FROM upward_check
JOIN ${ensIndexerSchema.v1Domain} pd
ON pd.id = upward_check.current_id
WHERE upward_check.depth < ${pathLength}
AND pd.label_hash = (${rawLabelHashPathArray})[${pathLength} - upward_check.depth]
)
SELECT leaf_id, current_id AS head_id
FROM upward_check
WHERE depth = ${pathLength}
) AS v1_path_check`,
)
.as("v1_path");
}

/**
* Compose a query for v2Domains that have the specified children path.
*
* For a search like "sub1.sub2.paren":
* - concrete = ["sub1", "sub2"]
* - partial = 'paren'
* - labelHashPath = [labelhash('sub2'), labelhash('sub1')]
*
* We find v2Domains matching the concrete path and return both:
* - leafId: the deepest child (label "sub1") - the autocomplete result, for ownership check
* - headId: the parent of the path (whose label should match partial "paren")
*
* Algorithm: Start from the deepest child (leaf) and traverse UP via registryCanonicalDomain.
* For v2, parent relationship is: domain.registryId -> registryCanonicalDomain -> parent domainId
*/
function v2DomainsByLabelHashPath(labelHashPath: LabelHashPath) {
// If no concrete path, return all domains (leaf = head = self)
// Postgres will optimize this simple subquery when joined
if (labelHashPath.length === 0) {
return ensDb
.select({
leafId: sql<ENSv2DomainId>`${ensIndexerSchema.v2Domain.id}`.as("leafId"),
headId: sql<ENSv2DomainId>`${ensIndexerSchema.v2Domain.id}`.as("headId"),
})
.from(ensIndexerSchema.v2Domain)
.as("v2_path");
}

// NOTE: using new Param as per https://github.com/drizzle-team/drizzle-orm/issues/1289#issuecomment-2688581070
const rawLabelHashPathArray = sql`${new Param(labelHashPath)}::text[]`;
const pathLength = sql`array_length(${rawLabelHashPathArray}, 1)`;

// Use a recursive CTE starting from the deepest child and traversing UP
// The query:
// 1. Starts with domains matching the leaf labelHash (deepest child)
// 2. Recursively joins parents via registryCanonicalDomain, verifying each ancestor's labelHash
// 3. Returns both the leaf (for result/ownership) and head (for partial match)
return ensDb
.select({
// https://github.com/drizzle-team/drizzle-orm/issues/1242
leafId: sql<ENSv2DomainId>`v2_path_check.leaf_id`.as("leafId"),
headId: sql<ENSv2DomainId>`v2_path_check.head_id`.as("headId"),
})
.from(
sql`(
WITH RECURSIVE upward_check AS (
-- Base case: find the deepest children (leaves of the concrete path)
-- and get their parent via registryCanonicalDomain
-- Note: JOIN (not LEFT JOIN) is intentional - we only match domains
-- with a complete canonical path to the searched FQDN
SELECT
d.id AS leaf_id,
rcd.domain_id AS current_id,
1 AS depth
FROM ${ensIndexerSchema.v2Domain} d
FROM ${ensIndexerSchema.domain} d
JOIN ${ensIndexerSchema.registryCanonicalDomain} rcd
ON rcd.registry_id = d.registry_id
JOIN ${ensIndexerSchema.v2Domain} rcd_parent
ON rcd_parent.id = rcd.domain_id AND rcd_parent.subregistry_id = d.registry_id
JOIN ${ensIndexerSchema.domain} parent
ON parent.id = rcd.domain_id AND parent.subregistry_id = d.registry_id
WHERE d.label_hash = (${rawLabelHashPathArray})[${pathLength}]

UNION ALL

-- Recursive step: traverse UP via registryCanonicalDomain
-- Note: JOIN (not LEFT JOIN) is intentional - see base case comment
-- Recursive step: traverse UP via registryCanonicalDomain, verifying each ancestor's
-- labelHash. The np.subregistry_id = pd.registry_id clause performs edge authentication.
SELECT
upward_check.leaf_id,
rcd.domain_id AS current_id,
np.id AS current_id,
upward_check.depth + 1
FROM upward_check
JOIN ${ensIndexerSchema.v2Domain} pd
JOIN ${ensIndexerSchema.domain} pd
ON pd.id = upward_check.current_id
JOIN ${ensIndexerSchema.registryCanonicalDomain} rcd
ON rcd.registry_id = pd.registry_id
JOIN ${ensIndexerSchema.v2Domain} rcd_parent
ON rcd_parent.id = rcd.domain_id AND rcd_parent.subregistry_id = pd.registry_id
JOIN ${ensIndexerSchema.domain} np
ON np.id = rcd.domain_id AND np.subregistry_id = pd.registry_id
WHERE upward_check.depth < ${pathLength}
AND pd.label_hash = (${rawLabelHashPathArray})[${pathLength} - upward_check.depth]
)
SELECT leaf_id, current_id AS head_id
FROM upward_check
WHERE depth = ${pathLength}
) AS v2_path_check`,
) AS domain_path_check`,
)
.as("v2_path");
.as("domain_path");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 FQDN searches silently return empty results for 2+ label names

The new domainsByLabelHashPath traverses upward via registryCanonicalDomain, but concrete ENSv1Registry rows (TLDs) have no registryCanonicalDomain entry — only virtual registries do. When pathLength ≥ 2 and the recursive step reaches a TLD (e.g. eth), the JOIN on rcd.registry_id = pd.registry_id finds no row, so depth = pathLength is never reached, and the CTE returns nothing.

Concretely, searching sub.eth. (FQDN — concrete=['sub','eth'], pathLength=2) traverses:

  1. depth=1: sub.etheth via virtual-registry RCD ✓
  2. depth=2: eth (registryId = concrete ENSv1Registry) → no RCD entry → JOIN fails

WHERE depth = 2 matches nothing; the query returns 0 rows.

The old v1DomainsByLabelHashPath used d.parent_id which handled null parents naturally: the recursive step for eth produced head_id = null, depth=2 was reached, and the result was returned correctly (with an empty partial filter). The new unified code removes parent_id but doesn't add a fallback for when the upward walk terminates at a root registry.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants